import json
import os.path
import time
import traceback
from copy import copy
from pathlib import Path
from threading import Thread

import chelper

state_name_dic = {
    "absolute_coord": 0,
    "absolute_extrude": 1,
    "base_position": 2,
    "homing_position": 3,
    "speed": 4,
    "speed_factor": 5,
    "extrude_factor": 6,
    "last_position": 7,
    "temp": 8,
    "bed_temp": 9,
    "gcode_file": 10,
    "file_position": 11,
    "fan_speed": 12,
    "gcode_now_new": 13,
    # "position_now": 14,

}

state_name_dic_rv = {str(v): k for k, v in state_name_dic.items()}


def start_pdb():
    def func():
        try:
            import web_pdb;
            web_pdb.set_trace(port=5555)
        except Exception as e:
            pass

    Thread(target=func).start()


class gb:
    printer = None


# start_pdb()


class GcodeExactState:
    """
    准确的当期gcode位置,和当前移动类别的gcode的执行进度记录
    """

    def __init__(self, printer, interval=0.1):
        try:
            self.interval = interval
            self.printer = printer
            self.mcu = self.printer.lookup_object('mcu')
            self.reactor = printer.get_reactor()
            self.toolhead = self.printer.lookup_object('toolhead')
            self.gcode_move = self.printer.lookup_object('gcode_move')
            self.gcode = self.printer.lookup_object('gcode')
            self.motion_report = self.printer.lookup_object("motion_report")
            self.gcmd = self.gcode.create_gcode_command("", "", {})
            self.gcode_set_now = None
            # 已经flush了的gcode列表,只包含移动类的gcode G1和G0
            self.gcode_queue = []
            # self.to_position = [0, 0, 0,0]
            self.position_now = [0, 0, 0, 0]
            self.file_position_now = 0
            # self.run()
        except Exception as e:
            raise self.gcmd.error(f"GcodeExactState error: {e}")

    def get_exact_print_time_now(self):
        """
        这个print_time_now是结合当前时间的实际正在打印的时间
        """
        return self.mcu.estimated_print_time(self.reactor.monotonic())

    # 1
    def handel_run_script(self, gcode):
        # 判断是否是移动命令
        if gcode.split(" ")[0].split("\n")[0].split(";")[0].upper() in ['G1', 'G0']:
            self.gcode_set_now = gcode
            # self.gcmd.respond_info(f"handel_run_script {gcode}")

    def handel_set_file_position(self, file_position_now):
        self.file_position_now = file_position_now

    def handel_set_move_gcode(self, move):
        move.gcode_set_now = self.gcode_set_now
        move.file_position = self.file_position_now
        self.gcode_set_now = None

    # 4
    def handel_set_gcode_drain_time(self, moves, print_time):
        self.clear_queue()
        if moves:
            move = moves[0]
            if hasattr(move, 'gcode_set_now') and move.gcode_set_now:
                move.print_time = print_time
                self.gcode_queue.append(move)

    def clear_queue(self):
        exact_print_time_now = self.get_exact_print_time_now()
        self.gcode_queue = [item for item in self.gcode_queue if item.print_time > exact_print_time_now]

    # def clac_last_position(self, move):
    #
    #     accel_t, cruise_t, decel_t, start_pos_x, start_pos_y, start_pos_z, axes_r_x, axes_r_y, axes_r_z, start_v, cruise_v, accel = move.accel_t, move.cruise_t, move.decel_t, \
    #         move.start_pos[0], move.start_pos[1], move.start_pos[2], move.axes_r[0], move.axes_r[1], move.axes_r[
    #         2], move.start_v, move.cruise_v, move.accel
    #     r_x, r_y, r_z = axes_r_x, axes_r_y, axes_r_z
    #     start_pos = move.start_pos
    #     move_t = accel_t
    #     if accel_t:
    #         move_t = accel_t
    #         start_v = start_v
    #         half_accel = .5 * accel
    #         move_time = accel_t
    #         move_dist = (start_v + half_accel * move_time) * move_time
    #         start_pos = [start_pos_x + r_x * move_dist, start_pos_y + r_y * move_dist, start_pos_z + r_z * move_dist]
    #
    #     if cruise_t:
    #         move_t = cruise_t
    #         start_v = cruise_v
    #         half_accel = 0.
    #         move_time = cruise_t
    #         move_dist = (start_v + half_accel * move_time) * move_time
    #         start_pos = [start_pos_x + r_x * move_dist, start_pos_y + r_y * move_dist, start_pos_z + r_z * move_dist]
    #     if decel_t:
    #         move_t = decel_t
    #         start_v = cruise_v
    #     start_x, start_y, start_z = start_pos
    #     accel=2*accel
    #     exact_print_time_now = self.get_exact_print_time_now()
    #     move_time = max(0., min(move_t, exact_print_time_now - move.print_time+move_t))
    #     dist = (start_v + .5 * accel * move_time) * move_time
    #     pos = [start_x + r_x * dist, start_y + r_y * dist,
    #            start_z + r_z * dist]
    #     velocity = start_v + accel * move_time
    #     return pos

    def clac_last_position(self):
        print_time = self.get_exact_print_time_now()
        ffi_main, ffi_lib = chelper.get_ffi()
        data = ffi_main.new('struct pull_move[1]')
        count = ffi_lib.trapq_extract_old(self.motion_report.trapqs['toolhead'].trapq, data, 1, 0., print_time)
        if not count:
            return None
        move = data[0]
        move_time = max(0., min(move.move_t, print_time - move.print_time))
        dist = (move.start_v + .5 * move.accel * move_time) * move_time;
        pos = (move.start_x + move.x_r * dist, move.start_y + move.y_r * dist,
               move.start_z + move.z_r * dist)
        velocity = move.start_v + move.accel * move_time
        return pos

    def get_status(self, eventtime=None):
        if self.gcode_queue:

            move = self.gcode_queue[0]
            position_now = self.clac_last_position()
            if position_now:
                self.position_now = list(position_now) + [self.position_now[-1]]
            else:
                position_now = self.position_now
            file_position = move.file_position
            gcode_set_now, last_position, to_position = move.gcode_set_now, move.start_pos, move.end_pos
            pos_d = [abs(last_position[i] - to_position[i]) for i in range(3)]
            ax = pos_d.index(max(pos_d))
            if pos_d[ax] > 0:
                ratio = (position_now[ax] - last_position[ax]) / (to_position[ax] - last_position[ax])
                if ratio > 0:
                    e_start = move.start_pos[-1]
                    e_end = move.end_pos[-1]
                    e = e_start + (e_end - e_start) * ratio
                    self.position_now[-1] = e
                    gcode_now_new = self.make_rest_move_gcode(gcode_set_now, ratio,
                                                              absolute_extrude=self.gcode_move.absolute_extrude)
                    return {
                        "position_now": self.position_now,
                        "gcode_now_new": gcode_now_new,
                        "file_position": file_position,
                    }

    def make_rest_move_gcode(self, gcode, processed_ratio, absolute=False, absolute_extrude=False, ):
        """
        把gcode执行的剩余部分合成一个新的gcode
        gcode需要是G1 G0命令，
        processed_ratio:已经执行完成的部分占比
        absolute,看是什么移动模式
        absolute_extrude:是否是相对挤出
        """
        gcode_bak = gcode
        # 解析G-code指令
        gcode = gcode.split(";")[0].split("\n")[0].upper()
        parts = [item for item in gcode.split(" ") if item]
        command = parts[0]  # 获取指令类型，如G0或G1
        if command not in ['G0', 'G1']:
            return gcode_bak
        params = {}
        for part in parts[1:]:
            key, value = part[0], float(part[1:])  # 将参数解析为键值对
            params[key] = value

        # 计算剩余运动
        remaining_movement = {}
        for axis, value in params.items():
            if axis in 'XYZ':
                remaining_movement[axis] = value * (1 - processed_ratio) if absolute else value
            else:
                remaining_movement[axis] = value

        if 'E' in params:
            if absolute_extrude:
                remaining_movement['E'] = params['E']
            else:
                remaining_movement['E'] = params['E'] * (1 - processed_ratio)
        # 构建新的G-code
        new_gcode = command
        for axis, value in remaining_movement.items():
            new_gcode += f" {axis}{value:.2f}"  # 保留两位小数
        return new_gcode

    # def _run(self):
    #     while 1:
    #         time.sleep(self.interval)
    #         # live_position = list(motion_status['live_position'])
    #         # self.position_now = copy(live_position)
    #         self.clear_queue()
    #         if self.gcode_queue:
    #             try:
    #                 move = self.gcode_queue[0]
    #                 position_now = self.clac_last_position()
    #                 if position_now:
    #                     # position_now = live_position
    #                     gcode_set_now, last_position, to_position, move_time, exact_print_time = move.gcode_set_now, move.start_pos, move.end_pos, move.min_move_t, move.print_time
    #                     pos_d = [abs(last_position[i] - to_position[i]) for i in range(3)]
    #                     ax = pos_d.index(max(pos_d))
    #                     if pos_d[ax] > 0:
    #                         ratio = (position_now[ax] - last_position[ax]) / (to_position[ax] - last_position[ax])
    #                         self.position_now = position_now
    #                         # self.gcmd.respond_info( f"gcode_now:{gcode_set_now},已移动:{int(ratio * 100)}%,live_position:
    #                         # {live_position},{exact_print_time_now}")
    #             except Exception as e:
    #                 self.gcmd.respond_info(f"GcodeExactState _run error :{traceback.format_exc()}")

    # def run(self):
    #     Thread(target=self._run).start()


class PowerResume:

    def __init__(self, printer, file_path="print_states.json", interval=0.4, use_exact_record=True):
        gb.printer = printer
        # use_exact_record为True则是更精确的模式
        self.use_exact_record = use_exact_record
        self.file_path = file_path
        self.interval = interval
        self.printer = printer
        self.gcode_move = self.printer.lookup_object('gcode_move')
        self.print_stats = self.printer.lookup_object('print_stats')
        self.toolhead = self.printer.lookup_object('toolhead')
        self.gcode = self.printer.lookup_object('gcode')
        self.fan = self.printer.lookup_object('fan', None)
        self.heater_bed = self.printer.lookup_object('heater_bed')
        self.extruder = self.printer.lookup_object('toolhead').get_extruder()
        self.virtual_sdcard = self.printer.lookup_object('virtual_sdcard')
        self.gcmd = self.gcode.create_gcode_command("", "", {})
        self.kin = self.toolhead.get_kinematics()
        self.gcode.register_command("POWER_RESUME", self.cmd_POWER_RESUME,
                                    desc=self.cmd_POWER_RESUME_help)
        if self.use_exact_record:
            self.ges = GcodeExactState(printer)
            help.ges = self.ges

    def _get_temp(self):
        """
        获取挤出机当前设置的温度,
        # todo:这里只有一个挤出头的温度，是否有影响？似乎不影响
        """
        temp = self.extruder.get_heater().target_temp
        return temp

    def _get_bed_temp(self):
        """
        获取热床当前设置的温度,
        """
        temp = self.heater_bed.heater.target_temp
        return temp

    def _get_fan_speed(self):
        """
        获取风扇速度
        """
        if self.fan:
            return self.fan.fan.last_fan_value

    def _get_gcode_file_info(self):
        """
        获取当前执行的gcode打印文件位置
        """
        file_name = Path(
            self.virtual_sdcard.file_path()).name if self.virtual_sdcard.file_path() else None
        return file_name, self.virtual_sdcard.next_file_position

    def _get_gcode_state(self):
        """
        获取gcode状态,里面有这些参数
        absolute_coord
        absolute_extrude
        base_position
        homing_position
        speed
        speed_factor
        extrude_factor
        last_position
        """
        self.gcode_move.cmd_SAVE_GCODE_STATE(self.gcmd)
        state = copy(self.gcode_move.saved_states['default'])
        return state

    def _save_kin_state(self):
        return self.kin.limits, self.kin.axes_min, self.kin.axes_max

    def check_has_state_file(self):
        return os.path.exists(self.file_path)

    def _save_state_thread(self):
        """
        定时保存打印状态的线程
        1.判断是否在打印，是的话，每2秒保存一次
        2.在打印完成的情况下，删除记录文件
        """
        # 如过开机打印过了，has_printed就是True
        has_printed = False
        time.sleep(30)
        # self.gcmd.respond_info("断电续打线程启动")
        if os.path.exists(self.file_path):
            self.gcmd.respond_info(
                "There are files that have not been printed yet,use cmd: POWER_RESUME to continue print")
        while True:
            # 正在打印中
            if self.virtual_sdcard.work_timer:
                has_printed = True
                try:
                    self.save_print_states()
                except Exception as e:
                    self.gcmd.respond_info(f"save_print_states error {e}")
            else:
                # 存在打印完成,并且当前没有打印则删除这个文件
                if has_printed:
                    if self.print_stats.state == 'complete':
                        if os.path.exists(self.file_path):
                            os.remove(self.file_path)

            time.sleep(self.interval)
            # self.gcmd.respond_info(f"{self.virtual_sdcard.work_timer}")

    def get_print_states(self):
        state = self._get_gcode_state()
        state['temp'] = self._get_temp()
        state['bed_temp'] = self._get_bed_temp()
        gcode_file, file_position = self._get_gcode_file_info()
        state['gcode_file'] = gcode_file
        state['file_position'] = file_position
        state['fan_speed'] = self._get_fan_speed()
        if self.use_exact_record:
            last_print_gcode_state = self.ges.get_status()
            if last_print_gcode_state:
                state['file_position'] = last_print_gcode_state['file_position']
                state['gcode_now_new'] = last_print_gcode_state['gcode_now_new']
                position_now = last_print_gcode_state['position_now']
                # state['last_position'] = list(position_now[0:3]) + [state['last_position'][-1]]
                state['last_position'] = position_now
            else:
                state['gcode_now_new'] = None
        # limits, axes_min, axes_max = self._save_kin_state()
        # state['limits'] = limits
        # state['axes_min'] = axes_min
        # state['axes_max'] = axes_max
        save_states = {state_name_dic[key]: value for key, value in state.items()}
        return save_states

    def save_print_states(self):
        save_states = self.get_print_states()
        with open(self.file_path, 'w') as f:
            json.dump(save_states, f)

    def save_static_print_states(self):
        pass

    def _restore_gcode_state(self, state):
        self.gcode_move.absolute_coord = state['absolute_coord']
        self.gcode_move.absolute_extrude = state['absolute_extrude']
        self.gcode_move.base_position = list(state['base_position'])
        self.gcode_move.homing_position = list(state['homing_position'])
        self.gcode_move.speed = state['speed']
        self.gcode_move.speed_factor = state['speed_factor']
        self.gcode_move.extrude_factor = state['extrude_factor']
        # self.gcode_move.base_position[-1] = 0
        # Restore the relative E position
        e_diff = self.gcode_move.last_position[3] - state['last_position'][3]
        self.gcode_move.base_position[3] += e_diff
        speed = self.gcode_move.speed
        # self.gcode_move.last_position[:3] = state['last_position'][:3]
        # self.gcode_move.move_with_transform(self.gcode_move.last_position, speed)

    def _restore_fan_state(self, state):
        if self.fan:
            self.fan.fan.set_speed_from_command(state.get('fan_speed', 0))

    def _restore_extruder_state(self, state, wait=True):
        pheaters = self.printer.lookup_object('heaters')
        pheaters.set_temperature(self.extruder.get_heater(), state.get("temp", 200), wait)

    def _restore_gcode_file(self, state):
        filename = state.get("gcode_file")
        vs = self.virtual_sdcard
        if vs.work_timer is not None:
            raise self.gcmd.respond_info("SD busy")
        if filename[0] == '/':
            filename = filename[1:]
        vs._load_file(self.gcmd, filename, check_subdirs=True)
        vs.file_position = state.get("file_position", 0)
        vs.do_resume()

    def _restore_kin(self, state):
        self.kin.limits = state.get("limits")
        self.kin.axes_min = state.get("axes_min")
        self.kin.axes_max = state.get("axes_max")

    def _restore_heater_bed_state(self, state, wait=True):
        """
        热床温度
        """
        pheaters = self.printer.lookup_object('heaters')
        pheaters.set_temperature(self.heater_bed.heater, state.get("bed_temp", 60), wait)

    def restore_states(self):
        try:
            if os.path.exists(self.file_path):
                try:
                    with open(self.file_path, 'rb') as f:
                        state = json.load(f)
                        state = {state_name_dic_rv[k]: v for k, v in state.items()}
                except Exception as e:
                    with open(self.file_path + ".tmp", 'rb') as f:
                        state = json.load(f)
                        state = {state_name_dic_rv[k]: v for k, v in state.items()}
                # self._restore_gcode_state(state)
                speed = self.gcode_move.speed
                # 预热喷头温度
                self._restore_extruder_state(state, True)
                self._restore_heater_bed_state(state, False)
                # 归零
                self.printer.lookup_object('homing').cmd_G28(self.gcmd)
                # self._restore_kin(state)
                # self.gcode.create_gcode_command("G28", "G28", {'X': '0', 'Y': '0', "Z": '0'})
                # self.gcode.run_script("G28 X Y")
                # 移动到0,0,z点等待加热
                self.toolhead.manual_move([0, 0, state['last_position'][2]], speed)
                self.toolhead.wait_moves()

                # 设置风扇
                self._restore_fan_state(state)
                # 设置热床
                self._restore_heater_bed_state(state)
                # 设置喷头温度
                self._restore_extruder_state(state)
                # 清理喷头的逻辑
                script = """
G1 F1800
M83
G1 X0 Y0 Z0.35
G1 X15 Y13
G1 X315 Y13 E15
G1 Z0.35
G1 x15 Y13
                                """
                self.gcode.run_script_from_command(script)
                self.toolhead.wait_moves()
                # 移动到0,0,z点等待加热
                self.toolhead.manual_move([0, 0, state['last_position'][2]], speed)
                self.toolhead.wait_moves()
                # 挤出
                # self.gcode_move.cmd_M83(self.gcode)
                # self.gcode_move.cmd_G1(self.gcode.create_gcode_command("G1", "G1", {'E': '10', 'F': '100'}))
                # self.toolhead.wait_moves()
                # 设置gcode状态并移动到上次打印结束位置
                self.toolhead.manual_move(state['last_position'][0:3], speed)

                self._restore_gcode_state(state)
                self.toolhead.wait_moves()
                if self.use_exact_record:
                    gcode_now_new = state.get('gcode_now_new')
                    help.gcode_now_new = gcode_now_new
                    # params = {item[0]: float(item[1:]) for item in gcode_now_new.split("\n")[0].split(";")[0].split(" ")
                    #           if item and item[0] in "XYZEF"}
                    # if gcode_now_new:
                    #     self.gcode_move.cmd_G1(self.gcode.create_gcode_command("G1", "G1", params))
                    # self.gcode.run_script(gcode_now_new)
                # 从上次的断电的打印位置开始打印
                self._restore_gcode_file(state)
            else:
                raise self.gcmd.error("no resume file")
        except Exception as e:
            raise self.gcmd.error(f"restore_states error: {e}")

    def cmd_POWER_RESUME(self, gcmd):
        try:
            self.restore_states()
        except Exception as e:
            s = traceback.format_exc()
            raise self.gcmd.error(f"POWER_RESUME error: {s}")

    cmd_POWER_RESUME_help = "power resume"

    def start(self):
        pass
        # Thread(target=self._save_state_thread).start()
